﻿using System;
using System.Collections.Generic;
using System.Linq;
using TSU.FAMC.ComputerEngineering.Classes.Enums;

namespace TSU.FAMC.ComputerEngineering.Classes.BooleanMaths.Fundamental
{
    /// <summary>
    /// Set of cubes with additional operations on it.
    /// </summary>
    public class CubeSet : HashSet<Cube>
    {
        // Fields

        /// <summary>
        /// Dimension of cubes in set.
        /// </summary>
        public int Dimension { get; private set; }

        /// <summary>
        /// The root of the equation D=0.
        /// </summary>
        private Cube _nullRoot;


        // Methods

        /// <summary>
        /// Default constructor.
        /// </summary>
        public CubeSet()
        {
            _nullRoot = new Cube();
        }

        /// <summary>
        /// Copy constructor.
        /// </summary>
        /// <param name="cubeSet"></param>
        public CubeSet(IEnumerable<Cube> cubeSet)
        {
            // copying cubes
            foreach (var cube in cubeSet)
            {
                Add(cube);
            }

            _nullRoot = new Cube(Dimension);
            for (var i = 0; i < _nullRoot.Dimension; i++)
            {
                _nullRoot[i] = BooleanValue.Undefined;
            }
        }

        /// <summary>
        /// Adds new item to the set.
        /// </summary>
        /// <param name="item">New cube item.</param>
        /// <returns>True if the item has been added successfully.</returns>
        public new bool Add(Cube item)
        {
            if (Count == 0)
            {
                // setting dimension
                Dimension = item.Length;
            }

            if (Count != 0)
            {
                if (item.Length != Dimension)
                {
                    throw new ArgumentException(
                        String.Format("Specified cube must has the same length as cubes in the set: {0}", Dimension));                    
                }
            }

            return base.Add(item);
        }

        /// <summary>
        /// Checks whether set contains the cube.
        /// </summary>
        /// <param name="searchCube">Cube object to search.</param>
        /// <returns>True if set contains the cube.</returns>
        public new bool Contains(Cube searchCube)
        {
            return this.Any(cube => cube == searchCube);
        }

        /// <summary>
        /// Checkes whether set of cubes (Sum Of Products) is a full boolean set by rounding FreeBDD.
        /// </summary>
        /// <param name="set">Set of cubes in current BDD tree.</param>
        /// <param name="varNumber">Number of fixed variable.</param>
        /// <param name="varValue">Fixed variable value.</param>
        /// <returns>True if Sum Of Products is a Constant1. False otherwise.</returns>
        public bool CheckSopIsFullSet(CubeSet set, int varNumber, bool varValue)
        {
            // checking whether the set is empty
            if (set.Count == 0)
            {
                return false;
            }

            var vectorLength = 0;

            // copying cubes
            var cubesCopy = new CubeSet();
            var isConstant1Flag = false;
            foreach (var cube in set)
            {
                if (vectorLength == 0)
                {
                    vectorLength = cube.Length;
                }

                if (cube.Rank == 0)
                {
                    // constant 1
                    isConstant1Flag = true;
                    break;
                }

                var cubeCopy = (Cube) cube.Clone();
                cubesCopy.Add(cubeCopy);
            }

            
            // reseting equation root
            if (varNumber < 0)
            {
                _nullRoot = new Cube(vectorLength);
                for (var i = 0; i < _nullRoot.Dimension; i++)
                {
                    _nullRoot[i] = BooleanValue.Undefined;
                }
            }

            if (isConstant1Flag)
            {
                return true;
            }

            var count0 = new int[vectorLength];
            var count1 = new int[vectorLength];


            // fixing variable
            var cubesToRemove = new CubeSet();
            if (varNumber >= 0)
            {
                if (!varValue)
                {
                    // fixing variable to false value
                    foreach (var cube in cubesCopy)
                    {
                        if (cube[varNumber] == BooleanValue.False)
                        {
                            cube[varNumber] = BooleanValue.Undefined;

                            // checking whether all bits are uncertain
                            if (cube.Rank == 0)
                            {
                                return true;
                            }
                        }
                        else if (cube[varNumber] == BooleanValue.True)
                        {
                            // deleting cube
                            cubesToRemove.Add(cube);
                        }
                    }
                }
                else
                {
                    // fixing variable to true value
                    foreach (var cube in cubesCopy)
                    {
                        if (cube[varNumber] == BooleanValue.True)
                        {
                            cube[varNumber] = BooleanValue.Undefined;

                            // checking whether all bits are uncertain
                            if (cube.Rank == 0)
                            {
                                return true;
                            }
                        }
                        else if (cube[varNumber] == BooleanValue.False)
                        {
                            // deleting cube
                            cubesToRemove.Add(cube);
                        }
                    }
                }
            }

            // removing redundant cubes
            foreach (var cube in cubesToRemove)
            {
                cubesCopy.Remove(cube);
            }

            if (cubesCopy.Count == 0)
            {
                return false;
            }

            // searching for the most frequent variable to fix it

            // reseting value counters
            for (var i = 0; i < vectorLength; i++)
            {
                count0[i] = 0;
                count1[i] = 0;
            }

            // searching for variable to fix
            var leader0 = 0;
            var leader1 = 0;
            var leader0Idx = -1;
            var leader1Idx = -1;
            var oneWay = false;

            // searching for cubes with only one determined variable
            foreach (var cube in cubesCopy)
            {
                if (cube.Rank != 1)
                {
                    continue;
                }

                // searching for determined variable
                for (var i = 0; i < vectorLength; i++)
                {
                    if (cube[i] == BooleanValue.Undefined)
                    {
                        continue;
                    }

                    if (cube[i] == BooleanValue.False)
                    {
                        leader0 = 1;
                        leader0Idx = i;
                    }
                    else
                    {
                        leader1 = 1;
                        leader1Idx = i;
                    }
                }

                oneWay = true;
                break;
            }


            if (!oneWay)
            {
                // counting number of 1 and 0 values for each variable to determing the leader in terms of frequency
                for (var i = 0; i < vectorLength; i++)
                {
                    foreach (var cube in cubesCopy)
                    {
                        if (cube[i] == BooleanValue.False)
                        {
                            count0[i]++;
                        }
                        else if (cube[i] == BooleanValue.True)
                        {
                            count1[i]++;
                        }
                    }
                }

                // searching for leaders
                for (var i = 0; i < vectorLength; i++)
                {
                    if (count0[i] <= leader0) continue;

                    leader0 = count0[i];
                    leader0Idx = i;
                }

                for (var i = 0; i < vectorLength; i++)
                {
                    if (count1[i] <= leader1) continue;

                    leader1 = count1[i];
                    leader1Idx = i;
                }
            }

            // fixing variable in the root of the equation
            _nullRoot[leader1 > leader0 ? leader1Idx : leader0Idx] = leader1 > leader0 ? BooleanValue.False : BooleanValue.True;

            // retrieving recursion results
            var leftRound = CheckSopIsFullSet(cubesCopy, leader1 > leader0 ? leader1Idx : leader0Idx,
                                                leader1 > leader0 ? false : true);
            if (!leftRound)
            {
                // root of D = 0 was found
                return false;
            }


            // fixing variable in the root of the equation
            _nullRoot[leader1 > leader0 ? leader1Idx : leader0Idx] = leader1 > leader0 ? BooleanValue.True : BooleanValue.False;

            var rightRound = CheckSopIsFullSet(cubesCopy, leader1 > leader0 ? leader1Idx : leader0Idx,
                                                 leader1 > leader0);
            return rightRound;
        }

        /// <summary>
        /// Checks whether set of cubes is a Constant1 by rounding FreeBDD tree to fix variables.
        /// </summary>
        public bool IsFullSet
        {
            get { return CheckSopIsFullSet(this, -1, false); }
        }

        /// <summary>
        /// Calculates a root of the equation D=0.
        /// </summary>
        public Cube EquationNullRoot
        {
            get 
            { 
                var result = CheckSopIsFullSet(this, -1, false);
                if (!result)
                {
                    // Null root found
                    return _nullRoot;
                }

                // D == 1, reseting a root
                for (var i = 0; i < _nullRoot.Dimension; i++)
                {
                    _nullRoot[i] = BooleanValue.Undefined;
                }
                return _nullRoot;
            }
        }

        /// <summary>
        /// Checks whether boolean set is orthogonal to cube by checking intersection.
        /// </summary>
        /// <param name="cube">Cube to check intersection with.</param>
        /// <returns></returns>
        public bool IsOrthogonalTo(Cube cube)
        {
            return this.All(setCube => setCube.IsOrthogonalTo(cube));
        }

        /// <summary>
        /// Checkes whether boolean set is orthogonal to another boolean set.
        /// </summary>
        /// <param name="set">Boolean set as a set of cubes.</param>
        /// <returns>True if sets are orthogonal.</returns>
        public bool IsOrthogonalTo(CubeSet set)
        {
            return set.All(IsOrthogonalTo);
        }

        /// <summary>
        /// Checks value equality for cube sets by rule: A lte B && B lte A.
        /// </summary>
        /// <param name="set1">First cube set.</param>
        /// <param name="set2">Second cube set.</param>
        /// <returns>True if sets are equal accurate within permutation.</returns>
        public static bool operator ==(CubeSet set1, CubeSet set2)
        {
            return !ReferenceEquals(set1, null) && set1.Equals(set2);
        }

        /// <summary>
        /// Checks value inequality of two cube sets.
        /// </summary>
        /// <param name="set1">First cube set.</param>
        /// <param name="set2">Second cube set.</param>
        /// <returns>False if sets are not equal.</returns>
        public static bool operator !=(CubeSet set1, CubeSet set2)
        {
            return !(set1 == set2);
        }

        /// <summary>
        /// Checks cube sets equality by rule: A lte B && B lte A.
        /// </summary>
        /// <param name="obj">Object to check equality with.</param>
        /// <returns>True if value of two cube sets are equal</returns>
        public bool Equals(CubeSet obj)
        {
            if (ReferenceEquals(null, obj)) return false;

            if (ReferenceEquals(this, obj)) return true;

            foreach (var cube in this)
            {
                if (!cube.IsCoveredBy(obj))
                {
                    return false;
                }
            }

            return obj.All(cube => cube.IsCoveredBy(this));
        }

        /// <summary>
        /// Checks equality with arbitrary object.
        /// </summary>
        /// <param name="obj">Object to check equality with.</param>
        /// <returns>True if cube set equals to object.</returns>
        public override bool Equals(object obj)
        {
            if (obj is CubeSet)
            {
                return Equals(obj as CubeSet);
            }

            return Equals(this, obj);
        }

        /// <summary>
        /// Returns integer hash code.
        /// </summary>
        /// <returns>Integer hash code.</returns>
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        /// <summary>
        /// Excludes cubes absorbed by other cubes.
        /// </summary>
        /// <returns></returns>
        public CubeSet ExcludeAbsorbedCubes()
        {
            var workingSet = new List<Cube>(this);
            var absorbedSet = new HashSet<Cube>();
            for (var i = 0; i < workingSet.Count; i++)
            {
                if (absorbedSet.Contains(workingSet[i]))
                {
                    continue;
                }

                for (var j = 0; j < workingSet.Count; j++)
                {
                    if (i == j || absorbedSet.Contains(workingSet[j]))
                    {
                        continue;
                    }

                    if (workingSet[i].Contains(workingSet[j]))
                    {
                        absorbedSet.Add(workingSet[j]);
                    }
                }
            }

            // removing absorbed cubes
            foreach (var cube in absorbedSet)
            {
                workingSet.Remove(cube);
            }

            return new CubeSet(workingSet);
        }
    }
}